/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.taglib.core.html;

import com.blandware.atleap.webapp.util.core.ApplicationResources;
import com.blandware.atleap.webapp.util.core.WebappConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorResources;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.taglib.TagUtils;
import org.apache.struts.validator.ValidatorPlugIn;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.Locale;


/**
 * <p>This class is designed to render a <label> tag for labeling your forms; it
 * adds an asterisk (*) for required fields.  It was originally written by Erik
 * Hatcher (http://www.ehatchersolutions.com/JavaDevWithAnt/).
 * </p>
 * <p>Modified in order to provide flexibility by <a href="mailto:sergey.zubtsovskiy@blandware.com">Sergey Zubtsovskiy</a>
 * </p>
 * <p>It is designed to be used as follows:
 * <pre>
 *  &lt;atleap:label property="username" key="core.user.form.username" /&gt;
 * </pre>
 * You may also specify <em>formName</em> attribute. By default it will be taken from enclosing
 * FormTag. If no such tag is found, JspException will be thrown.
 * <br />
 * You MUST specify <em>property</em> attribute, but <em>key</em> attribute may be omitted.
 * In such case field key will be constructed from form name and property as follows:
 * <pre>
 * <div align="center" style="font-style: italic;">formKey = formName.property</div>
 * </pre>
 * Also, if you specify tag body, its content will be treated as message. Key
 * and body cannot be specified at once.
 * </p>
 * <p>
 * Full list of allowed attributes:
 * <ul>
 * <li>
 * <b>property</b> - required - property this label is created for
 * </li>
 * <li>
 * <b>key</b> - bundle key for our field, it's used to get label; by default
 * it's <b>formName</b>.<b>property</b> (form name and property separated with
 * period). Cannot be specified if body is not empty.
 * </li>
 * <li>
 * <b>formName</b> - name of form bean to which property belongs. By default is
 * taken from enclosing <em>form</em> tag.
 * </li>
 * <li>
 * <b>colon</b> - whether or not to put colon (':') after label. Default is
 * true.
 * </li>
 * <li>
 * <b>helpTip</b> - whether or not to create a helptip event for this label.
 * Default is false.
 * </li>
 * <li>
 * <b>style</b> - CSS style to be applied to this element
 * </li>
 * <li>
 * <b>styleClass</b> - CSS class for label in normal state, if not specified and
 * field is required, then "required" will be used
 * </li>
 * <li>
 * <b>errorClass</b> - CSS class for label when validation error has occured.
 * By default it is "error".
 * </li>
 * </ul>
 * </p>
 * <p><a href="LabelTag.java.html"><i>View Source</i></a></p>*
 *
 * @author Eric Hatcher <a href="http://www.ehatchersolutions.com/JavaDevWithAnt/">http://www.ehatchersolutions.com/JavaDevWithAnt/</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.7 $ $Date: 2008/06/24 06:39:08 $
 * @jsp.tag name="label" body-content="scriptless"
 */
public class LabelTag extends SimpleTagSupport {
	protected transient final Log log = LogFactory.getLog(LabelTag.class);

	/**
	 * Bundle key for our field
	 */
	protected String key = null;

	/**
	 * CSS class for label in normal state, if not specified and
     * field is required, then "required" will be used
	 */
	protected String styleClass = null;

	/**
	 * CSS style to be applied to this element
	 */
	protected String style = null;

	/**
	 * CSS class for label when validation error has occured. By default it is "error"
	 */
	protected String errorClass = null;

	/**
	 * Whether or not to put colon after label
	 */
	protected boolean colon = true;

	/**
	 * Whether or not create a helptip event for this label
	 */
	protected boolean helpTip = false;

	/**
	 * Property this label is created for
	 */
	protected String property = null;

	/**
	 * Name of form bean to which property belongs. By default is taken from enclosing FormTag
	 */
	protected String formName = null;

    /**
     * Processes the tag
     *
     * @throws JspException
     */
	public void doTag() throws JspException, IOException {

		PageContext pageContext = (PageContext) getJspContext();
		HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
		ApplicationResources applicationResources = ApplicationResources.getInstance(pageContext.getServletContext());

		// Look up this key to see if it's a field of the current form
		boolean requiredField = false;
		boolean validationError = false;
		TagUtils tagUtils = TagUtils.getInstance();

		ValidatorResources resources =
		        (ValidatorResources) pageContext.getServletContext()
		        .getAttribute(ValidatorPlugIn.VALIDATOR_KEY);

		Locale locale =
		        (Locale) pageContext.findAttribute(Globals.LOCALE_KEY);

		if ( locale == null ) {
			locale = Locale.getDefault();
		}

		FormTag formTag =
		        (FormTag) pageContext.getAttribute(WebappConstants.FORM_KEY,
		                PageContext.REQUEST_SCOPE);

		// if form tag was not found in request, try to find ancestor
		if ( formTag == null ) {
			formTag = (FormTag) findAncestorWithClass(this, FormTag.class);
		}

		// if still not found - throw an exception
		if ( formTag == null ) {
			String errorMessage = "Couldn't find enclosing 'form' tag";
			if ( log.isErrorEnabled() ) {
				log.error(errorMessage);
			}
			throw new JspTagException(errorMessage);
		}

		if ( formName == null ) {
			formName = formTag.getBeanName();
		}

        boolean keyIsGiven;

		if ( key == null || key.length() == 0 ) {
            keyIsGiven = false;
			StringBuffer strBuffer = new StringBuffer(formName);
			strBuffer.append(".");
			strBuffer.append(property);
			key = strBuffer.toString();
		} else {
            keyIsGiven = true;
        }

		if ( resources != null ) {
			Form form = resources.getForm(locale, formName);

			if ( form != null ) {
				Field field = form.getField(property);

				if ( field != null ) {
					if ( field.isDependency("required") ) {
						requiredField = true;
					}
				}
			}
		}

		ActionMessages errors = tagUtils.getActionMessages(pageContext, Globals.ERROR_KEY);

		StringBuffer valMessage = new StringBuffer();

		if ( errors != null ) {
			// check for errors from the validator
			Iterator valIterator = errors.get(property);

			while ( valIterator.hasNext() ) {
				validationError = true;

				ActionMessage error = (ActionMessage) valIterator.next();

				// Retrieve the message string we are looking for
				valMessage.append(applicationResources.getMessage(request, error.getKey(), error.getValues()));
			}
		}

		JspFragment body = getJspBody();
        StringWriter stringWriter = new StringWriter();
		if ( body != null ) {
			body.invoke(stringWriter);
		}
        String bodyContent = stringWriter.toString();

        boolean bodyIsGiven = bodyContent != null && bodyContent.length() > 0;

        if (keyIsGiven && bodyIsGiven) {
            throw new JspException("key attribute and body cannot be specified at once");
        }

		// Retrieve the message string we are looking for
		String message;
        if (bodyIsGiven) {
            message = bodyContent;
        } else {
            message = applicationResources.getMessage(request, key);
        }

        StringBuffer valError = new StringBuffer();

		if ( message == null ) {
			message = "???" + key + "???";
		} else if ( validationError ) {
			valError.append(valMessage);
		}

		String cssClass = null;
		if ( styleClass != null ) {
			cssClass = styleClass;
		} else if ( requiredField ) {
			cssClass = "required";
		}

		String cssErrorClass = (errorClass != null) ? errorClass : "error";
		StringBuffer label = new StringBuffer();

		if ( (message == null) || "".equals(message.trim()) ) {
			label.append("");
		} else {
			label.append("<label for=\"" + property + "\"");

			if ( validationError ) {
				label.append(" class=\"" + cssErrorClass + "\"");
			} else if ( cssClass != null ) {
				label.append(" class=\"" + cssClass + "\"");
			}

			if ( style != null && style.length() != 0 ) {
				label.append(" style=\"" + style + "\"");
			}

			label.append(">" + ((requiredField) ? "* " : "") + message);
			String marker = (locale.equals(Locale.FRENCH)) ? " :" : ":";
			label.append(((colon) ? marker : "") + "</label>");

			if ( valError.length() > 0 ) {
				String error = valError.toString();

				if ( helpTip ) {
					// strip out any single or double quotes
					String htmlFriendly = StringUtils.replace(error, "'", "\\\'");
					htmlFriendly =
					        StringUtils.replace(htmlFriendly, "\"", "\\\"");
					label.append(" <a class=\"errorLink\" href=\"?\" onclick=\"showHelpTip(event, '");

					label.append(htmlFriendly + "', false); return false\" ");
					label.append("onmouseover=\"showHelpTip(event, '");
					label.append(htmlFriendly + "', false); return false\" ");
					label.append("onmouseout=\"hideHelpTip(event); return false\">");
				}


				label.append("<img class=\"validationWarning\" ");

				String context =
				        ((HttpServletRequest) pageContext.getRequest())
				        .getContextPath();

				label.append("src=\"" + context);
				label.append(applicationResources.getMessage(request, "core.commons.icon.warning.img"));
				label.append("\" />");
				if ( helpTip ) {
					label.append("</a>");
				}
			}
		}

		// Print the retrieved message to our output writer
		tagUtils.write(pageContext, label.toString());

	}

	/**
     * Sets bundle key corresponding to the field
     *
     * @param key bundle key corresponding to the field
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Bundle key for our field"
	 */
	public void setKey(String key) {
		this.key = key;
	}

	/**
     * Sets whether or not to put colon after label
     *
     * @param colon whether to put colon after label
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="boolean"
	 * description="Whether or not put colon after label"
	 */
	public void setColon(boolean colon) {
		this.colon = colon;
	}

	/**
     * Sets style class
     *
     * @param styleClass style class to set
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS class for label in normal state"
	 */
	public void setStyleClass(String styleClass) {
		this.styleClass = styleClass;
	}

	/**
     * Sets CSS style
     *
	 * @param style CSS style to set
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS style to be applied to this element"
	 */
	public void setStyle(String style) {
		this.style = style;
	}

	/**
     * Sets error style class
     *
     * @param errorClass error style class to set
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="CSS class for label when validation error has occured"
	 */
	public void setErrorClass(String errorClass) {
		this.errorClass = errorClass;
	}

	/**
     * Sets whether to create a help tip
     *
     * @param helpTip whether to create a help tip
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="boolean"
	 * description="Whether or not create a helptip event for this label"
	 */
	public void setHelpTip(boolean helpTip) {
		this.helpTip = helpTip;
	}

	/**
     * Sets property name for which this label is created
     *
	 * @param property property name to set
	 * @jsp.attribute required="true"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Property this label is created for"
	 */
	public void setProperty(String property) {
		this.property = property;
	}

	/**
     * Sets form name to which this label belongs
     *
	 * @param formName form name to set
	 * @jsp.attribute required="false"
	 * rtexprvalue="true"
	 * type="java.lang.String"
	 * description="Name of form bean to which property belongs."
	 */
	public void setFormName(String formName) {
		this.formName = formName;
	}

}
